Skip to content

feat(metadata-editor): wire new MetadataTaxonomyPicker into redesigned sidebar#4683

Merged
mergify[bot] merged 4 commits into
masterfrom
taxonomy-picker-update-for-preview
Jul 14, 2026
Merged

feat(metadata-editor): wire new MetadataTaxonomyPicker into redesigned sidebar#4683
mergify[bot] merged 4 commits into
masterfrom
taxonomy-picker-update-for-preview

Conversation

@AnnaStepchenko

@AnnaStepchenko AnnaStepchenko commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Wires the new MetadataTaxonomyPicker (from @box/metadata-taxonomy-picker, consumed via @box/metadata-editor) into the redesigned metadata sidebar behind a new metadata.taxonomyPicker.enabled feature flag. When the flag is off, the sidebar keeps rendering the legacy MultilevelTaxonomyField — no behavior change for existing consumers.

What's in this PR

Fetcher — src/elements/content-sidebar/fetchers/metadataTaxonomyFetcher.ts

  • New createTaxonomyItemsService factory that returns per-field TaxonomyItemsServices (browse + search) backed by the existing /metadata_templates/.../options endpoint.
  • Requests are scoped to the correct level based on the field's optionsRules.selectableLevels:
  • Single-level picker (selectableLevels has exactly one entry): loads that level directly (level=N, no ancestor) and marks every returned node as a leaf so no drill-down chevron renders.
  • Multi-level picker (selectableLevels.length > 1): starts at level 1 and drills down on demand, tracking each returned node's level so it can request parentLevel + 1 with ancestor=<parentId>.

Sidebar wiring

  • MetadataSidebarRedesign.tsx — reads the new metadata.taxonomyPicker.enabled flag, resolves the editing template's field config (levels + optionsRules.selectableLevels), memoizes a createTaxonomyItemsService instance, and threads it into MetadataInstanceEditor.
  • MetadataInstanceEditor.tsx — accepts the new isMetadataTaxonomyPickerEnabled and createTaxonomyItemsService props and forwards them to MetadataInstanceForm (as isTaxonomyPickerEnabled and createTaxonomyItemsService).

Summary by CodeRabbit

  • New Features
    • Added an optional taxonomy picker to metadata editing, enabled via a feature flag.
    • Introduced multi-level taxonomy browsing with drill-down, breadcrumbs, search, and pagination.
  • Bug Fixes
    • Standardized taxonomy option fields (including selectable/ancestor labeling) across API response variations.
    • Improved hierarchy handling, including leaf-node behavior and safer fallbacks when drilling into unknown parents.
  • Tests
    • Expanded coverage for the taxonomy picker feature-flag wiring and taxonomy navigation, caching, and pagination behaviors.

@AnnaStepchenko AnnaStepchenko requested review from a team as code owners July 9, 2026 14:38
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5bf422fa-e0cc-4db7-b668-95183ad835e2

📥 Commits

Reviewing files that changed from the base of the PR and between 86164ef and a92cf17.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

Walkthrough

Adds taxonomy response normalization and a taxonomy items service, wires it through the metadata sidebar and editor behind a feature flag, updates package versions, and adds coverage for fetching, drill-down, search, pagination, and component integration.

Changes

Taxonomy Picker Service

Layer / File(s) Summary
Taxonomy service and response normalization
src/elements/content-sidebar/fetchers/metadataTaxonomyFetcher.ts
Normalizes taxonomy fields and adds configurable level resolution, ancestor mapping, child detection, browsing, searching, and pagination.
Feature flag and editor wiring
src/elements/content-sidebar/MetadataSidebarRedesign.tsx, src/elements/content-sidebar/MetadataInstanceEditor.tsx, package.json
Propagates taxonomy-picker configuration through the editor, resolves template field settings, and updates taxonomy package versions.
Fetcher and service validation
src/elements/content-sidebar/__tests__/*
Updates response-shape fixtures and tests taxonomy service behavior and sidebar feature-flag wiring.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MetadataSidebarRedesign
  participant MetadataInstanceEditor
  participant MetadataInstanceForm
  participant createTaxonomyItemsService
  participant getMetadataOptions
  MetadataSidebarRedesign->>MetadataInstanceEditor: pass feature flag and service creator
  MetadataInstanceEditor->>MetadataInstanceForm: pass taxonomy-picker props
  MetadataInstanceForm->>createTaxonomyItemsService: request taxonomy nodes
  createTaxonomyItemsService->>getMetadataOptions: fetch scoped options
  getMetadataOptions-->>createTaxonomyItemsService: return options and marker
  createTaxonomyItemsService-->>MetadataInstanceForm: return normalized nodes
Loading

Possibly related PRs

Suggested reviewers: tjuanitas, jfox-box, jakubkida

Poem

I’m a rabbit in the taxonomy row,
Mapping roots where the breadcrumbs grow.
With flags switched on and nodes in sight,
I hop through levels, left to right.
Tests keep watch as markers flow—
Carrot-approved, the picker’s aglow!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: wiring the new MetadataTaxonomyPicker into the redesigned sidebar.
Description check ✅ Passed The description is detailed and covers the main wiring, fetcher changes, feature flag, and prop forwarding.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch taxonomy-picker-update-for-preview

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/elements/content-sidebar/__tests__/metadataTaxonomyFetcher.test.ts (1)

623-630: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the positional level arg here to fully cover the "safe default" contract.

This test verifies options has no level, but does not assert the positional level argument (mock.calls[0][4]). Given getNodes always passes a computed childLevel positionally (see metadataTaxonomyFetcher.ts), the unrestricted-browse guarantee isn't actually exercised. Adding an assertion on mock.calls[0][4] would catch the divergence from searchNodes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/elements/content-sidebar/__tests__/metadataTaxonomyFetcher.test.ts`
around lines 623 - 630, The safe-default test only checks the options object and
misses the positional level argument passed by
createTaxonomyItemsService/getNodes. Update the test in
metadataTaxonomyFetcher.test.ts to also assert the fifth mock call argument
(mock.calls[0][4]) for the expected unrestricted-browse childLevel behavior, so
it covers the same contract that getNodes and searchNodes rely on.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/elements/content-sidebar/__tests__/metadataTaxonomyFetcher.test.ts`:
- Around line 623-630: The safe-default test only checks the options object and
misses the positional level argument passed by
createTaxonomyItemsService/getNodes. Update the test in
metadataTaxonomyFetcher.test.ts to also assert the fifth mock call argument
(mock.calls[0][4]) for the expected unrestricted-browse childLevel behavior, so
it covers the same contract that getNodes and searchNodes rely on.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5f2f1982-179b-40c7-b4a6-5912790a1336

📥 Commits

Reviewing files that changed from the base of the PR and between 1ebc004 and 0ee8334.

📒 Files selected for processing (5)
  • src/elements/content-sidebar/MetadataInstanceEditor.tsx
  • src/elements/content-sidebar/MetadataSidebarRedesign.tsx
  • src/elements/content-sidebar/__tests__/metadataTaxonomyFetcher.test.ts
  • src/elements/content-sidebar/fetchers/metadataTaxonomyFetcher.ts
  • src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx

@alexkirillovtech alexkirillovtech left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@socket-security

socket-security Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​@​box/​metadata-taxonomy-picker@​3.1.8 ⏵ 3.1.25771008999 +150
Updatednpm/​@​box/​metadata-editor@​2.2.11 ⏵ 2.2.2885100899950

View full report

Comment on lines +407 to +409
createTaxonomyItemsService={
isMetadataTaxonomyPickerEnabled ? taxonomyItemsServiceCreator : undefined
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this list is alphabetical

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the prop from between taxonomyOptionsFetcher and template up to right after areAiSuggestionsAvailable 👍 86164ef

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should be cautious of how many VRTs we're adding. how many of these are necessary as VRTs vs RTL test?

i.e. does the shared feature already cover the UI states? if so, then let's convert the tests to RTL to save costs of VRTs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, the picker's actual UI states are already covered by VRTs in the shared feature. Added a new focused RTL suite MetadataSidebarRedesign-taxonomyPicker.test.tsx that verifies the integration & removed VRTs 86164ef

Comment on lines +138 to +143
if (typeof option.has_children === 'boolean') {
return option.has_children;
}
if (typeof option.hasChildren === 'boolean') {
return option.hasChildren;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this type correct? I don't see these properties on MetadataOptionEntry. why does it have both?

@AnnaStepchenko AnnaStepchenko Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced it with a new local type TaxonomyEntry that spells out every field we actually read, including both the snake_case and camelCase variants, with a comment explaining why both exist 86164ef

};

// Prefers backend `has_children`; falls back to depth. `forceLeaf` disables drill-down.
const getHasChildren = (option: MetadataOptionEntry, maxLevel?: number, forceLeaf?: boolean): boolean => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does forceLeaf mean? if it disables "drill-down" then isn't it forcing the root instead of the leaf?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A "leaf" here means "no children / no drill-down chevron", which is what single-level picker mode requires for every row. I understand why it can look weird, renamed it to isSingleLevelMode and updated its docstring

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +199 to +210
for (const entry of entries) {
if (entry?.id != null && entry.level != null) {
nodeLevelById.set(String(entry.id), Number(entry.level));
}
if (Array.isArray(entry?.ancestors)) {
for (const ancestor of entry.ancestors) {
if (ancestor && typeof ancestor === 'object' && ancestor.id != null && ancestor.level != null) {
nodeLevelById.set(String(ancestor.id), Number(ancestor.level));
}
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new changes in this file has a lot of loose equality checks with != null. do we know why are all the properties from fieldConfig optional?

are we able to add defaults to fieldConfig to simplify the logic instead of having many conditionals / ternaries / null coalescing operators?

something like:

const { levels = [], selectableLevels = [], ... } = resolveField?.(templateKey, fieldKey);

const maxLevel: number = ... // instead of `getMaxLevel` returning number | undefined, can it just return number?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, simplified the factory setup using destructuring defaults 86164ef

has_children?: boolean;
hasChildren?: boolean;
selectable?: boolean;
ancestors?: Array<TaxonomyEntryAncestor | string | null | undefined>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this type correct? an ancestor can be all of these?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would it be an array of null or undefined?

Comment on lines +132 to +133
// API may return level as number ('1') or string ('"1"'); callers coerce.
level: number | string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this true? the API could return either type?

// Tracks levels of returned nodes so drill-down can request `parentLevel + 1`.
const nodeLevelById = new Map<string, number>();

const rememberLevels = (entries: TaxonomyEntry[]): void => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does rememberLevels mean?

Comment on lines +228 to +235
if (entry?.id != null && entry.level != null) {
nodeLevelById.set(String(entry.id), Number(entry.level));
}
for (const ancestor of entry?.ancestors ?? []) {
if (ancestor && typeof ancestor === 'object' && ancestor.id != null && ancestor.level != null) {
nodeLevelById.set(String(ancestor.id), Number(ancestor.level));
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still see a lot of loose equality throughout this file. if we're looping through entries, when would entry?.id != null ever be false?

similar for entry.level != null? based on the TaxonomyEntry type, these properties always exist

@tjuanitas tjuanitas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments but no major concerns

@AnnaStepchenko

Copy link
Copy Markdown
Contributor Author

https://github.com/Mergifyio queue

@mergify

mergify Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-07-14 08:59 UTC · Rule: Automatic strict merge · triggered by @AnnaStepchenko with the @mergifyio queue command
  • Checks skipped · PR is already up-to-date
  • Merged2026-07-14 08:59 UTC · at a92cf17acc64dd7e48ec44d77e72373b796e574e · squash

This pull request spent 16 seconds in the queue, including 3 seconds running CI.

Required conditions to merge

@mergify mergify Bot added the queued label Jul 14, 2026
@mergify mergify Bot merged commit c5e0f55 into master Jul 14, 2026
14 checks passed
@mergify mergify Bot deleted the taxonomy-picker-update-for-preview branch July 14, 2026 08:59
@mergify mergify Bot removed the queued label Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants